/*
 * MIT License
 *
 * Copyright (c) 2023-present, tangli
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package tony.aliyun.oss

import com.aliyun.oss.OSS
import com.aliyun.oss.OSSClientBuilder
import com.aliyun.oss.model.LifecycleRule
import com.aliyun.oss.model.MatchMode
import com.aliyun.oss.model.ObjectMetadata
import com.aliyun.oss.model.PolicyConditions
import com.aliyun.oss.model.SetBucketLifecycleRequest
import java.io.InputStream
import java.time.LocalDateTime
import tony.aliyun.oss.autoconfigure.AliyunOssProperties
import tony.core.SpringContexts
import tony.core.codec.Encoding
import tony.core.exception.ApiException
import tony.core.utils.sanitizedPath
import tony.core.utils.toDate
import tony.core.utils.toJsonString
import tony.core.utils.uuid

/**
 * 阿里云oss Manager
 * @author tangli
 * @date 2023/09/28 19:53
 */
public data object AliYunOssManager {
    private val aliyunOssProperties: AliyunOssProperties by SpringContexts.getBeanByLazy()

    private val ossClient: OSS by lazy {
        OSSClientBuilder().build(
            aliyunOssProperties.endpoint,
            aliyunOssProperties.accessKeyId,
            aliyunOssProperties.accessKeySecret
        )
    }

    /**
     * Uploads the file to the Bucket from the @{link InputStream} with the ObjectMetadata information
     * @param [path] 路径
     * @param [name] 名称
     * @param [inputStream] 输入流
     * @param [metadata] 元数据
     * @return [String]
     * @author tangli
     * @date 2023/09/28 19:53
     */
    @JvmStatic
    @JvmOverloads
    public fun putObject(
        path: String,
        name: String,
        inputStream: InputStream,
        metadata: ObjectMetadata? = null,
    ): String =
        ossClient.run {
            val sanitizedPath = sanitizedPath("$path/$name").removePrefix("/")
            putObject(aliyunOssProperties.bucketName, sanitizedPath, inputStream, metadata)
            "https://${aliyunOssProperties.bucketName}.${aliyunOssProperties.endpoint}/$sanitizedPath"
        }

    /**
     * 生成客户端直接上传凭证
     *
     * 生成PostObject方式的上传凭证，包含预览URL
     *
     * @param [request] 上传请求
     * @return [OssUploadCredentialResp] 上传凭证
     * @author tangli
     * @date 2025/01/04 16:30
     */
    @JvmStatic
    public fun generateUploadCredential(request: OssUploadRequest): OssUploadCredentialResp {
        // 生成临时对象键
        val tempObjectKey =
            sanitizedPath(
                listOf(
                    "ggfwpt",
                    request.path,
                    "${uuid()}.${request.fileName.substringAfterLast('.', "")}"
                ).joinToString("/")
            )
        val expiration = LocalDateTime.now().plusSeconds(request.duration.toMinutes()).toDate()

        val uploadUrl = "https://${aliyunOssProperties.bucketName}.${aliyunOssProperties.endpoint}"

        val policyConditions = PolicyConditions()
        policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 1, request.maxFileSize.toBytes())
        policyConditions.addConditionItem(MatchMode.Exact, PolicyConditions.COND_KEY, tempObjectKey)
        val postPolicy = ossClient.generatePostPolicy(expiration, policyConditions)
        val signature = ossClient.calculatePostSignature(postPolicy)
        val previewUrl = "https://${aliyunOssProperties.bucketName}.${aliyunOssProperties.endpoint}/$tempObjectKey"
        return OssUploadCredentialResp(
            tempObjectKey = tempObjectKey,
            uploadUrl = uploadUrl,
            policy = Encoding.BASE64.encodeToString(postPolicy.toJsonString()),
            signature = signature,
            accessKeyId = aliyunOssProperties.accessKeyId,
            previewUrl = previewUrl
        )
    }

    /**
     * 固化临时文件到正式路径
     *
     * 将临时文件复制到正式路径，成功返回最终URL
     *
     * @param [tempObjectKey] 临时对象键
     * @return [String] 最终文件的预览URL
     * @throws [RuntimeException] 当固化失败时抛出异常
     * @author tangli
     * @date 2025/01/04 16:30
     */
    @JvmStatic
    public fun finalizeObject(tempObjectKey: String): String {
        // 验证是否为临时对象
        if (!tempObjectKey.startsWith("temp/")) {
            throw IllegalArgumentException("对象键不是临时对象: $tempObjectKey")
        }

        try {
            // 计算最终对象键：将temp/替换为prod/
            val finalObjectKey = tempObjectKey.replaceFirst("temp/", "prod/")

            // 执行服务器端复制
            ossClient.copyObject(
                aliyunOssProperties.bucketName,
                tempObjectKey,
                aliyunOssProperties.bucketName,
                finalObjectKey
            )

            // 返回最终文件的预览URL
            return "https://${aliyunOssProperties.bucketName}.${aliyunOssProperties.endpoint}/$finalObjectKey"
        } catch (e: Exception) {
            throw RuntimeException("固化文件失败: ${e.message}", e)
        }
    }

    /**
     * 设置临时文件的生命周期规则
     * @param [expirationDays] 过期天数，默认7天
     * @author tangli
     * @date 2025/01/04 16:00
     */
    @JvmStatic
    @JvmOverloads
    public fun setupTempFileLifecycle(expirationDays: Int = 7) {
        try {
            // 获取现有的生命周期配置
            val existingRules =
                try {
                    ossClient.getBucketLifecycle(aliyunOssProperties.bucketName).toMutableList()
                } catch (_: Exception) {
                    // 如果没有现有配置，创建新的空列表
                    mutableListOf<LifecycleRule>()
                }

            // 检查是否已存在temp规则
            val tempRuleIndex = existingRules.indexOfFirst { it.id == "temp-files-cleanup" }

            // 创建新的temp规则
            val tempRule =
                LifecycleRule().apply {
                    id = "temp-files-cleanup"
                    prefix = "temp/"
                    status = LifecycleRule.RuleStatus.Enabled
                    setExpirationDays(expirationDays)
                }

            // 更新或添加规则
            if (tempRuleIndex >= 0) {
                existingRules[tempRuleIndex] = tempRule
            } else {
                existingRules.add(tempRule)
            }

            // 设置新的生命周期配置
            val request = SetBucketLifecycleRequest(aliyunOssProperties.bucketName)
            request.lifecycleRules = existingRules
            ossClient.setBucketLifecycle(request)
        } catch (e: Exception) {
            throw ApiException("设置生命周期规则失败: ${e.message}", e)
        }
    }
}
